define(async () => {
    const util = await require("../util");
    const Scene = await require("Scene");
    const Layer = await require("Layer");
    const EventEmitter = await require("EventEmitter");

    const KEY_ALIAS = {
        ArrowLeft: "left",
        ArrowRight: "right",
        ArrowUp: "up",
        ArrowDown: "down",
        " ": "space"
    };
    const PREVENT_DEFAULT_KEYS = ["space", "left", "right", "up", "down", "tab", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "s"];
    const KEY_STATE = {
        PRESSED: Symbol("PRESSED"),
        REPEAT_PRESSED: Symbol("REPEAT_PRESSED"),
        RELEASED: Symbol("RELEASED"),
        DOWN: Symbol("DOWN"),
        UP: Symbol("UP")
    };
    const MOUSE_BUTTONS = ["left", "middle", "right", "back", "forward"];
    const MOUSE_BUTTON_STATE = {
        PRESSED: Symbol("PRESSED"),
        RELEASED: Symbol("RELEASED"),
        DOWN: Symbol("DOWN"),
        UP: Symbol("UP")
    };

    /**
     * 舞台类
     * 
     * 一般全局有且只有一个实例，需指定挂载的DOM元素才能工作
     */
     class Stage extends EventEmitter {

        dom;  //舞台DOM元素
        width;  //舞台宽度
        height;  //舞台高度
        fps;  //舞台帧率
        layers = [];  //舞台图层实例集合
        scenes = [];  //舞台场景实例集合
        currentScene = null;  //当前场景
        lastScene = null;  //上个场景
        inputtedChars = [];  //已输入的字符集合
        keyStates = {};  //按键状态映射
        mouseStates = {};  //鼠标状态映射

        constructor(dom, options = {}) {
            super();
            util.assert(util.isDiv(dom), new TypeError("dom must be an HTMLDivElement"));
            util.assert(util.isObject(options), new TypeError("options must be an Object"));
            const { width, height, fps } = options;
            util.assert(util.isUndefined(width) || util.isNumber(width), new TypeError("width must be an Number"));
            util.assert(util.isUndefined(height) || util.isNumber(height), new TypeError("height must be an Number"));
            this.dom = dom;
            this.width = util.defaultTo(width, util.defaultTo(this.dom.clientWidth, 1920));
            this.height = util.defaultTo(height, util.defaultTo(this.dom.clientHeight, 1080));
            this.dom.style.width = `${this.width}px`;
            this.dom.style.height = `${this.height}px`;
            this.fps = util.defaultTo(fps, 60);
            this.resize();  //重设大小
            window.onresize = () => this.resize();  //监听resize事件重设大小
            this.inputInit();  //输入初始化
        }

        /**
         * 渲染当前场景
         */
        render() {
            if(!this.currentScene)
                return;
            this.emit("render");
            this.currentScene.render();  //渲染当前场景
            this.keyStatesReset();  //按键状态重置
            this.mouseStatesReset();  //鼠标状态重置
        }

        /**
         * 添加图层到舞台
         * 
         * @param {Layer} layer 图层实例
         * @return {Stage} 舞台实例
         */
        addLayer(layer) {
            util.assert(Layer.isInstance(layer), new TypeError("layer must be an Layer instance"));
            layer.init(this.width, this.height);  //初始化图层
            if(!this.layers.length)
                this.dom.appendChild(layer.canvas);
            else
                this.dom.insertBefore(layer.canvas, this.layers[this.layers.length - 1].canvas);
            this.layers[layer.id] = layer;
            this.layers.push(layer);
            return this;
        }

        /**
         * 添加多个图层到舞台
         * 
         * @param  {...Layer[]} layers 图层实例集合
         * @return {Stage} 舞台实例
         */
        addLayers(...layers) {
            layers.forEach(layer => this.addLayer(layer));
            return this;
        }

        /**
         * 添加场景到舞台
         * 
         * 场景添加后并不会直接渲染，还需要调用 Stage.transitionScene 函数才能将场景转换进来
         * 
         * @param {Scene} scene 场景实例
         * @return {Stage} 舞台实例
         */
        addChild(scene) {
            util.assert(Scene.isInstance(scene), new TypeError("scene must be an Scene instance"));
            scene.parent = this;  //将场景父级设置为舞台
            this.scenes[scene.id] = scene;
            this.scenes.push(scene);
            return this;
        }

        /**
         * 添加多个场景到舞台
         * 
         * @param  {...Scene[]} scenes 场景实例集合
         * @return {Stage} 舞台实例
         */
        addChilds(...scenes) {
            scenes.forEach(scene => this.addChild(scene));
            return this;
        }

        /**
         * 从舞台移除场景
         * 
         * 如果被移除的场景是当前正在渲染的场景将中断渲染
         * 
         * @param {Scene} scene 场景实例
         * @return {Stage} 舞台实例
         */
        removeChild(scene) {
            util.assert(Scene.isInstance(scene), new TypeError("scene must be an Scene instance"));
            //查找和清除舞台中对应的场景
            delete this.scenes[scene.id];
            const index = this.scenes.findIndex(_scene => _scene.id === scene.id);
            if(index !== -1)
                this.scenes.splice(index, 1);
            //如果被清除场景为当前场景则需要销毁
            if(this.currentScene && this.currentScene.id == scene.id) {
                this.currentScene.clear();
                this.currentScene = null;
            }
            return this;
        }

        /**
         * 从舞台移除多个场景
         * 
         * @param  {...Scene[]} scenes 场景实例集合
         * @return {Stage} 舞台实例
         */
        removeChilds(...scenes) {
            scenes.forEach(scene => scene.removeChild(scene));
            return this;
        }

        /**
         * 场景转换
         * 
         * 从一个场景进入另一个场景时需要调用此函数进行转场
         * 
         * @param {String} sceneId 准备进入的场景ID
         * @param {String} transitionName 转场效果名称
         * @return {Stage} 舞台实例
         */
        async transitionScene(sceneId, transitionName) {
            util.assert(util.isString(sceneId), new TypeError("sceneId must be an String"));
            util.assert(Scene.isInstance(this.scenes[sceneId]), `scene ${sceneId} was not found on stage`);
            const scene = this.scenes[sceneId];
            await scene.preprocessing();  //场景预处理
            this.lastScene = this.currentScene;  //将当前场景临时放到上一个场景中
            this.currentScene = scene;  //当前场景切换为新场景
            return this;
        }

        inputInit() {
            window.addEventListener("keyup", e => {
                const key = KEY_ALIAS[e.key] || e.key;
                this.keyStates[key] = KEY_STATE.RELEASED;
            });
            window.addEventListener("keydown", e => {
                const key = KEY_ALIAS[e.key] || e.key;
                PREVENT_DEFAULT_KEYS.includes(key.toLowerCase()) && e.preventDefault();
                if(key.length === 1)
                    this.inputtedChars.push(key);
                else if(key === "space")
                    this.inputtedChars.push(" ");
                if(e.repeat)
                    this.keyStates[key] = KEY_STATE.REPEAT_PRESSED;
                else
                    this.keyStates[key] = KEY_STATE.PRESSED;
            });
            window.addEventListener("mousedown", e => {
                const button = MOUSE_BUTTONS[e.button];
                button && (this.mouseStates[button] = MOUSE_BUTTON_STATE.PRESSED);
            });
            window.addEventListener("mouseup", e => {
                const button = MOUSE_BUTTONS[e.button];
                button && (this.mouseStates[button] = MOUSE_BUTTON_STATE.RELEASED);
            });
            // window.addEventListener("touchstart", e => {
            //     const button = MOUSE_BUTTONS[e.button];
            //     button && (this.mouseStates[button] = MOUSE_BUTTON_STATE.PRESSED);
            // });
            // window.addEventListener("touchend", e => {
            //     const button = MOUSE_BUTTONS[e.button];
            //     button && (this.mouseStates[button] = MOUSE_BUTTON_STATE.RELEASED);
            // });
        }

        keyStatesReset() {
            for(let key in this.keyStates) {
                let state = this.keyStates[key];
                if(state === KEY_STATE.PRESSED || state === KEY_STATE.REPEAT_PRESSED)
                    state = KEY_STATE.DOWN;
                else if(state === KEY_STATE.RELEASED)
                    state = KEY_STATE.UP;
                this.keyStates[key] = state;
            }
        }

        mouseStatesReset() {
            for(let button in this.mouseStates) {
                let state = this.mouseStates[button];
                if(state === MOUSE_BUTTON_STATE.PRESSED)
                    state = MOUSE_BUTTON_STATE.DOWN;
                else if(state === MOUSE_BUTTON_STATE.RELEASED)
                    state = MOUSE_BUTTON_STATE.UP;
                this.mouseStates[button] = state;
            }
        }

        getInputtedChars() {
            return [...this.inputtedChars];
        }

        onKeyUp(key, callback) {
            util.assert(util.isString(key), "key must be an String");
            util.assert(util.isFunction(callback), "callback must be an Function");
            this.on("render", () => this.isKeyUp(key) && callback());
        }

        onKeyRelease(key, callback) {
            util.assert(util.isString(key), "key must be an String");
            util.assert(util.isFunction(callback), "callback must be an Function");
            this.on("render", () => this.isKeyReleased(key) && callback());
        }

        onKeyDown(key, callback) {
            util.assert(util.isString(key), "key must be an String");
            util.assert(util.isFunction(callback), "callback must be an Function");
            this.on("render", () => this.isKeyDown(key) && callback());
        }

        onKeyPress(key, callback) {
            util.assert(util.isString(key), "key must be an String");
            util.assert(util.isFunction(callback), "callback must be an Function");
            this.on("render", () => this.isKeyPressed(key) && callback());
        }

        onKeyPressRepeat(key, callback) {
            util.assert(util.isString(key), "key must be an String");
            util.assert(util.isFunction(callback), "callback must be an Function");
            this.on("render", () => this.isKeyPressRepeat(key) && callback());
        }

        onMouseDown(button, callback) {
            util.assert(util.isFunction(callback), "callback must be an Function");
            this.on("render", () => this.isMouseDown(button) && callback());
        }

        onMousePress(button, callback) {
            util.assert(util.isFunction(callback), "callback must be an Function");
            this.on("render", () => this.isMousePressed(button) && callback());
        }
        
        onMouseUp(button, callback) {
            util.assert(util.isFunction(callback), "callback must be an Function");
            this.on("render", () => this.isMouseUp(button) && callback());
        }

        onMouseRelease(button, callback) {
            util.assert(util.isFunction(callback), "callback must be an Function");
            this.on("render", () => this.isMouseReleased(button) && callback());
        }

        onTouchStart() {

        }

        onTouchEnd() {

        }

        onCharInput(callback) {
            util.assert(util.isFunction(callback), "callback must be an Function");
            this.on("render", () => {
                this.getInputtedChars().forEach(char => callback(char));
                this.inputtedChars = [];
            });
        }

        isKeyUp(key) {
            return this.keyStates[key] === KEY_STATE.UP;
        }

        isKeyDown(key) {
            const state = this.keyStates[key];
            return state === KEY_STATE.PRESSED || state === KEY_STATE.REPEAT_PRESSED || state === KEY_STATE.DOWN;
        }

        isKeyPressed(key) {
            return this.keyStates[key] === KEY_STATE.PRESSED;
        }

        isKeyPressRepeat(key) {
            return this.keyStates[key] === KEY_STATE.PRESSED || this.keyStates[key] === KEY_STATE.REPEAT_PRESSED;
        }

        isKeyReleased(key) {
            return this.keyStates[key] === KEY_STATE.RELEASED;
        }

        isMouseUp(button) {
            return this.keyStates[button] === MOUSE_BUTTON_STATE.UP;
        }

        isMouseDown(button) {
            return this.mouseStates[button] === MOUSE_BUTTON_STATE.PRESSED || this.mouseStates[button] === MOUSE_BUTTON_STATE.DOWN;
        }

        isMousePressed(button) {
            return this.mouseStates[button] === MOUSE_BUTTON_STATE.PRESSED;
        }

        isMouseReleased(button) {
            return this.mouseStates[button] === MOUSE_BUTTON_STATE.RELEASED;
        }

        /**
         * 重设舞台大小
         * 
         * 使用缩放和偏移坐标让画面始终保持设置的比例并居中
         */
        resize() {
            let scale = 1;
            if(window.innerWidth > this.width) {
                if(window.innerHeight < this.height)
                    scale = parseFloat((window.innerHeight / this.height).toFixed(3));
            }
            else {
                scale = parseFloat((window.innerWidth / this.width).toFixed(3));
                if(window.innerHeight < this.height)
                    scale = Math.min(scale, parseFloat((window.innerHeight / this.height).toFixed(3)));
            }
            this.dom.style.top = `${window.innerHeight / 2 - this.height * scale / 2}px`;
            this.dom.style.left = `${window.innerWidth / 2 - this.width * scale / 2}px`;
            this.dom.style.transform = `scale(${scale})`;
        }

        /**
         * 判断是否Stage实例
         * 
         * @param {Any} value 
         * @return {Boolean}
         */
        static isInstance(value) {
            return value instanceof Stage;
        }

    }

    return Stage;
});